/******************************************************************************
*
* This file is part of FreeTrack (http://code.google.com/p/qtfreetrack).
* BY Willy Gardiol (willy@gardiol.org)
*
* FreeTrack is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* FreeTrack is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Foobar.  If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/

#include "workerprecachemap.h"
#include "configuration.h"
#include "image/image.h"

// Since chaning the chunk_size will invalidate the entire map cache, we
// define this as a static value!
#define CHUNK_SIZE 400

#define MAKE_CHUNK_KEY(xa12,ya12,za12) za12*100000+ya12*1000+xa12

WorkerPrecacheMap::WorkerPrecacheMap(const QString& filename,
                                     const QString& id,
                                     const QString& title) :
    WorkerProcess(1, title)
{
    image_filename = filename;
    image_id = id;
    source_image = NULL;
    image_height = image_width = 0;
}

WorkerPrecacheMap::~WorkerPrecacheMap()
{
    deallocateChunkImages();
}

bool WorkerPrecacheMap::do_stuff()
{
    // Check for image existance
    if ( !QFile::exists( image_filename ) )
    {
        setErrorString( tr("The map file must exists") );
        return false;
    }

    // Open the image file
    source_image = Image::LoadImage( image_filename );
    if ( source_image == NULL )
    {
        setErrorString( tr("Invalid image!") );
        return false;
    }
    // Load image size
    source_image->startReading( 1, image_width, image_height );

    // Determine maximum zoom factor (the zoom that will fit the entire image into the chunks)
    // We support MAXIMUM zoom == 256, so we cannot use higher zooms.
    int max_zoom_level = 0;
    max_zoom_level = qMax( 1,(int)ceil( (double)qMax(image_width, image_height) / (double)CHUNK_SIZE ) );
    max_zoom_level =   ( max_zoom_level > 256 ) ? 256 :
                       ( max_zoom_level > 128 ) ? 256 :
                       ( max_zoom_level > 64 ) ? 128 :
                       ( max_zoom_level > 32 ) ? 64 :
                       ( max_zoom_level > 16 ) ? 32:
                       ( max_zoom_level > 8 ) ? 16 :
                       ( max_zoom_level > 4 ) ? 8 :
                       ( max_zoom_level > 2 ) ? 4 : max_zoom_level;

    // Calculate n. of chunks that fits into the image at every zoom level
    QList<int> zoom_levels;
    QHash<int,int> chunks_rows;
    QHash<int,int> chunks_cols;
    for (int z = 1; z <= max_zoom_level; z *= 2)
    {
        zoom_levels.append( z );
        chunks_cols.insert(z, ceil( (qreal)image_width / (qreal)(CHUNK_SIZE*z) ) );
        chunks_rows.insert(z, ceil( (qreal)image_height / (qreal)(CHUNK_SIZE*z) ) );
    }


    QString path = configuration->defaultPath() + "/map_storage/" + image_id;
    if ( !QFile::exists( path ) )
        QDir().mkpath( path );

    QHash<int, QString> chunk_filenames;
    // Build filenames:
    int chunk_col,chunk_row,max_col;
    foreach( int z, zoom_levels )
        for ( chunk_row = 0; chunk_row < chunks_rows.value(z,0); chunk_row++ )
            for ( chunk_col = 0; chunk_col < chunks_cols.value(z,0); chunk_col++ )
                chunk_filenames.insert( MAKE_CHUNK_KEY(chunk_col, chunk_row, z),
                  path + QString("/%1_%2_%3.ftc").arg( z )
                                                 .arg( chunk_col )
                                                 .arg( chunk_row ) );
    int nChunks = 0;
    int totChunks = chunk_filenames.size();
    QString processingText = tr("Processed %1 / %2 chunks...");
    setMinMaxProgress( totChunks );
    updateText(0, processingText.arg( nChunks ) .arg( totChunks ) );
    updateProgress(nChunks);

    QString msg, tmp_filename;
    Image *tmp_image;
    uchar* scanline, *chunk_scanline;
    int output_height, output_width, remaining_rows, remaining_cols, current_row;

    // Iterate for all zoom levels
    foreach( int z, zoom_levels )
    {
        // Store maximum nunber of chunks for this zoom level
        max_col = chunks_cols.value(z,0);

        // Open source image...
        if ( source_image != NULL )
            deallocateChunkImages();
        source_image = Image::LoadImage( image_filename );
        // Get ready to read image data...
        source_image->startReading( z, output_width, output_height );

        // Force to open all destination images on first loop
        current_row = CHUNK_SIZE+1;
        // We start from the topmost row of chunks
        chunk_row = 0;
        // Iterate all scanlines of the image
        for ( int scanline_number = 0; scanline_number < output_height; scanline_number++ )
        {   // Read scanline from image

            scanline = source_image->readScanline();

            // Is scanline valid?
            if ( scanline != NULL )
            {
                // When we hit the CHUNK_SIZE row its time to close all upened chunks and open the new ones!
                if ( current_row > CHUNK_SIZE )
                {   // Track the last row, when we will put less than CHUNK_SIZE rows in the chunks
                    remaining_rows = output_height - scanline_number;
                    // Close all already opened chunks
                    if ( !chunk_images.isEmpty() )
                    {
                        foreach (int key, chunk_images.keys() )
                        {
                            nChunks++;
                            updateText(0, processingText.arg( nChunks ) .arg( totChunks ) );
                            updateProgress( nChunks );
                            Image::FreeImage( chunk_images.take( key ) );
                        }
                        if ( please_terminate )
                            break;
                    }
                    // Now, check and open any missing chunk...
                    remaining_cols = output_width;
                    for ( chunk_col = 0; chunk_col < max_col; chunk_col++ )
                    {
                        tmp_filename = chunk_filenames.value( MAKE_CHUNK_KEY( chunk_col, chunk_row, z ) );
                        tmp_image = Image::CreateImage( tmp_filename );
                        tmp_image->startWriting( qMin(CHUNK_SIZE, remaining_cols),
                                                 qMin(CHUNK_SIZE, remaining_rows) );
                        chunk_images.insert( chunk_col, tmp_image );
                        remaining_cols -= CHUNK_SIZE;
                    }
                    // reset row count
                    current_row = 0;
                    // we moved to next chunk row!
                    chunk_row++;
                } // end of re-open chunks

                // Now write to all chunks...
                if ( !chunk_images.isEmpty() )
                {
                    for ( chunk_col = 0; chunk_col < max_col; chunk_col++ )
                    {
                        tmp_image = chunk_images.value( chunk_col, NULL );
                        if ( tmp_image != NULL )
                        {
                            // Get a pointer to the part of the scanline we need to save in this chunk
                            chunk_scanline = &scanline[ chunk_col * CHUNK_SIZE * COLOR_CHANNELS ];
                            tmp_image->writeScanline( chunk_scanline );
                        }
                        qApp->processEvents( QEventLoop::AllEvents );
                        if ( please_terminate )
                            break;
                    }
                }
                current_row++;
            }
            else // scanline is NULL - error!
            {                
                setErrorString( tr("Unable to read source image!") );
                // Close and remove all chunk files still open
                deallocateChunkImages();
                return false;
            }
            qApp->processEvents( QEventLoop::AllEvents );
            if ( please_terminate )
                break;
        }

        // Close and remove all chunk files still open
        deallocateChunkImages();

        qApp->processEvents( QEventLoop::AllEvents );
        if ( please_terminate )
            break;
    }
    // Close and remove all chunk files still open
    deallocateChunkImages();
    return true;
}

/*
bool WorkerPrecacheMap::findMissing()
{
    int tot_chunks = 0;
    foreach (int zoom, chunks_rows.keys() )
        tot_chunks += chunks_rows.value(zoom,0)*chunks_cols.value(zoom,0);
    QString msg = tr("Analizing cached chunks...");
    int curr = 0;
    foreach ( QString filename, chunk_filenames )
    {
        sendUpdate( msg, 0, 0, tot_chunks, curr++ );
        if ( please_terminate )
            break;
        else if ( !chunkValid( filename ) )
        {
            sendUpdate( tr("Missing chunk detected!"), 0, 0, tot_chunks, tot_chunks );
            return true;
        }
    }
    sendUpdate( msg, 0, 0, tot_chunks, tot_chunks );
    return false;
}

bool WorkerPrecacheMap::chunkValid(const QString& cfm )
{
    QFileInfo qfi(cfm);
    // By spitting the tests we speed up a bit.
    return !qfi.exists() ? false :
            qfi.size() <= 1 ? false :
            qfi.lastModified() < source_timestamp ? false : true;
}*/

void WorkerPrecacheMap::deallocateChunkImages()
{
    foreach (int key, chunk_images.keys() )
        Image::FreeImage( chunk_images.take( key ) );
    if ( source_image != NULL )
        Image::FreeImage( source_image );
    source_image = NULL;
}

QSize WorkerPrecacheMap::getSize(int & cs )
{
    cs = CHUNK_SIZE;
    return QSize( image_width, image_height );
}
